* Cutting of agentic loop based on max_iterations configured.#2307
Open
printSamarth wants to merge 1 commit into
Open
* Cutting of agentic loop based on max_iterations configured.#2307printSamarth wants to merge 1 commit into
printSamarth wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a max_iterations configuration to Agent that bounds the number of "model turn → tool call → tool result" cycles per invocation, with a dedicated MaxIterationsReachedException raised when the limit is exceeded.
Motivation. Today the event loop runs until the LLM decides to stop. In production this exposes two risks:
Infinite reasoning loops — a tool error or confusing prompt can send the model into a hallucination loop, calling tools repeatedly without ever reaching end_turn.
Unpredictable latency / cost — without an explicit depth limit, a single user request can trigger many model turns and burn unbounded tokens.
API:
max_iterations is None by default (preserves existing behavior). Positive integers only; 0, negatives, non-ints, and booleans are rejected at construction time.
Implementation.
New MaxIterationsReachedException in strands.types.exceptions carrying iterations and max_iterations for structured logging.
New max_iterations constructor kwarg on Agent, validated to be a positive int or None.
The event loop tracks invocation_state["event_loop_iteration"] and the guard is enforced at the top of event_loop_cycle, before the next model call — so an over-limit cycle never consumes an additional model invocation. The exception is added to the bubble-up list so it isn't wrapped in EventLoopException.
Iteration semantics. One iteration = one model turn (not one tool call). Parallel tool use counts as a single iteration. This matches what the cap is intended to bound (reasoning depth / API calls), and avoids penalizing models that parallelize independent tool calls.
Related Issues
Resolves 2298
Documentation PR
N/A : happy to follow up with a docs PR once this merges.
Type of Change
New feature
Testing
Added the following coverage:
tests/strands/types/test_exceptions.py : construction, inheritance, and metadata propagation for MaxIterationsReachedException.
tests/strands/agent/test_agent.py : constructor validation (default None, positive int, rejection of 0 / -1 / 1.5 / "5" / True), plus end-to-end tests through the public API using MockedModelProvider:
runaway tool loop is bounded and raises with correct metadata
normal completion succeeds when within the budget
iteration counter resets between separate invocations
tests/strands/event_loop/test_event_loop.py : limit exceeded raises before the next model call (model.stream.call_count asserted), within-limit completes normally, None disables the check, and a pre-set counter trips the guard on the very first cycle.
A small number of existing tests that pinned the exact shape of invocation_state keys passed to callbacks were updated to include the new event_loop_iteration key consistent with how event_loop_cycle_id is already exposed.
Apart from this manualy tested by integrating it into a local project to verify end-to-end behavior.
hatch run prepareChecklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.